package com.siyoumi.component;

import com.siyoumi.component.http.XHttpContext;
import com.siyoumi.util.XDate;
import com.siyoumi.util.XJson;
import com.siyoumi.util.XStr;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.*;
import org.redisson.client.codec.StringCodec;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

@Slf4j
@Service
public class XRedis {
    @Autowired(required = false)
    @Qualifier("redisson")
    //@Resource(name = "redisson")
    //@Getter
    private RedissonClient redisson;


    static public XRedis getBean() {
        return XSpringContext.getBean(XRedis.class);
    }


    public RSemaphore getSemaphore(String key) {
        return redisson.getSemaphore(key);
    }

    public RPermitExpirableSemaphore getExpirableSemaphore(String key) {
        return redisson.getPermitExpirableSemaphore(key);
    }

    /**
     * lua
     */
    public RScript getScript() {
        return redisson.getScript();
    }

    public RTopic getTopic(String key) {
        return redisson.getTopic(key, StringCodec.INSTANCE);
    }

    public RMap<String, Object> getMap(String key) {
        return redisson.getMap(key, StringCodec.INSTANCE);
    }

    /**
     * 单键操作
     *
     * @param key
     */
    public RBucket<String> getBucket(String key) {
        return redisson.getBucket(key, StringCodec.INSTANCE);
    }

    /**
     * 列表
     *
     * @param key
     */
    public RMap<String, String> getList(String key) {
        return redisson.getMap(key, StringCodec.INSTANCE);
    }

    /**
     * 栈 或者 队列
     * addFirst     往头部加
     * addLast      往尾部加
     * pollFirst    头部出
     * pollLast     尾部出
     *
     * @param key
     */
    public RDeque<String> getDeque(String key) {
        return redisson.getDeque(key, StringCodec.INSTANCE);
    }

    /**
     * 锁
     *
     * @param key
     */
    public RLock getLock(String key) {
        return redisson.getLock(key);
    }


    public Boolean exists(String key) {
        return getBucket(key).isExists();
    }


    public Boolean del(String key) {
        return getBucket(key).delete();
    }

    /**
     * 执行删除，匹配前缀
     * 如：token:*
     *
     * @param start
     */
    public Long deleteByPattern(String start) {
        return redisson.getKeys().deleteByPattern(start);
    }

    /**
     * val值等于才执行删除
     *
     * @param key
     * @param val
     */
    public Boolean delIfEqualsVal(String key, String val) {
        String luaScript = "local currValue = redis.call('get', KEYS[1]); " +
                "if currValue == ARGV[1] then " +
                "redis.call('del', KEYS[1]); " +
                "return 1; " +
                "end;" +
                "return 0; ";
        List<Object> keys = Collections.singletonList(key);
        return getScript().eval(RScript.Mode.READ_WRITE, luaScript, RScript.ReturnType.BOOLEAN, keys, val);
    }

    /**
     * key不存在时，才能设置成功
     *
     * @param key
     * @param val
     * @param timeoutSeconds
     */
    public Boolean setIfExists(String key, String val, long timeoutSeconds) {
        Duration between = XDate.between(XDate.now(), XDate.now().plusSeconds(timeoutSeconds));
        return getBucket(key).setIfAbsent(val, between);
    }

    /**
     * 设置值，x秒后过期
     *
     * @param key
     * @param val
     * @param timeoutSeconds 过期秒数
     */
    public void setEx(String key, String val, long timeoutSeconds) {
        getBucket(key).set(val, timeoutSeconds, TimeUnit.SECONDS);
    }

    /**
     * 某时间点过期
     *
     * @param key
     * @param val
     * @param expirsTime 点时间点
     */
    public void setEx(String key, String val, LocalDateTime expirsTime) {
        Duration between = XDate.between(XDate.now(), expirsTime);
        long s = between.toSeconds();
        if (s <= 0) {
            s = 0;
        }
        setEx(key, val, s);
    }

    public String get(String key) {
        return (String) getBucket(key).get();
    }

    /**
     * 获取
     *
     * @param key
     */
    public String getAndDel(String key) {
        return getBucket(key).getAndDelete();
    }

    /**
     * 加数量
     *
     * @param key
     * @param inc
     */
    public Long increment(String key, Long inc) {
        return redisson.getAtomicLong(key).addAndGet(inc);
    }

    public Long increment(String key) {
        return increment(key, 1L);
    }

    /**
     * 加数量（不能大于max）
     *
     * @param key
     * @param max
     * @param second 过期时间（秒）
     */
    public Boolean increment(String key, int max, int second) {
        String luaScript = "local currValue = redis.call('get', KEYS[1]); " +
                //key未赋值
                "if not currValue then " +
                "redis.call('incr', KEYS[1]); " +
                "redis.call('EXPIRE', KEYS[1], ARGV[2]); " +
                "return 1; " +
                "end;" +
                // 大于max，不再加
                "if tonumber(currValue) >= tonumber(ARGV[1]) then " +
                "return 0; " +
                "end;" +
                //
                "redis.call('incr', KEYS[1]); " +
                "return 1; ";
        List<Object> keys = Collections.singletonList(key);
        return XRedis.getBean().getScript().eval(RScript.Mode.READ_WRITE, luaScript, RScript.ReturnType.BOOLEAN, keys, max, second);
    }

    /**
     * 设置过期
     *
     * @param key
     * @param timeoutSeconds 过期-秒
     */
    public void expire(String key, Integer timeoutSeconds) {
        getBucket(key).expire(Duration.ofSeconds(timeoutSeconds));
    }

    /**
     * 减数量（不能小于0）
     *
     * @param key
     */
    public Boolean decrement(String key) {
        String luaScript = "local currValue = redis.call('get', KEYS[1]); " +
                //key未赋值
                "if not currValue then " +
                "return 0; " +
                "end;" +
                // <= 0 不操作
                "if tonumber(currValue) > 0 then " +
                "redis.call('decr', KEYS[1]); " +
                "return 1; " +
                "end;" +

                "return 0; ";
        List<Object> keys = Collections.singletonList(key);
        return XRedis.getBean().getScript().eval(RScript.Mode.READ_WRITE, luaScript, RScript.ReturnType.BOOLEAN, keys);
    }

    /**
     * 左入队
     *
     * @param key
     * @param val
     */
    public void lPush(String key, String val) {
        getDeque(key).addFirst(val);
    }

    /**
     * 右入队
     *
     * @param key
     * @param val
     */
    public void rPush(String key, String val) {
        getDeque(key).addLast(val);
    }

    /**
     * 左出队
     *
     * @param key
     */
    public String lPop(String key) {
        return getDeque(key).pollFirst();
    }

    /**
     * 右出队
     *
     * @param key
     */
    public String rPop(String key) {
        return getDeque(key).pollLast();
    }


    public String getAndSetData(String key, Function<String, Object> func, boolean getCache) {
        log.debug("RedisKey: {}", key);
        String json = null;
        if (getCache) {
            log.debug("从缓存中获取顺序:XHttpContext->Redis->func");

            json = XHttpContext.get(key);
            if (XStr.hasAnyText(json)) {
                return json;
            }

            json = get(key);
            if (XStr.hasAnyText(json)) {
                return json;
            }
        }

        Object entity = func.apply(key);
        if (entity != null) {
            json = XJson.toJSONString(entity);
            //设置redis
            setEx(key, json, 600);
            //放入请求上下文
            XHttpContext.set(key, json);
        }

        return json;
    }

    public <T> T getAndSetData(String key, Function<String, Object> func, Class<T> clazz) {
        String json = getAndSetData(key, func, true);
        return XJson.parseObject(json, clazz);
    }

    public <T> T getAndSetData(String key, Function<String, Object> func, Class<T> clazz, Boolean getCache) {
        String json = getAndSetData(key, func, getCache);
        return XJson.parseObject(json, clazz);
    }
}
